﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Mono.Reflection;

namespace DelegateToExpression
{
	public static class DelegateExtensions
	{
        // ReSharper disable UnusedMember.Local
        private static Expression<TDelegate> DelegateLambda<TDelegate>(TDelegate delegat) where TDelegate : class
        // ReSharper restore UnusedMember.Local
        {
            return ToExpression<TDelegate>(delegat as Delegate);
        }

	    public static Expression ToExpression(this Delegate @delegate)
	    {
            // call generic DelegateLambda
            var method = typeof(DelegateExtensions).GetMethod("DelegateLambda", BindingFlags.NonPublic | BindingFlags.Static);
	        var genericMethod = method.MakeGenericMethod(@delegate.GetType());
            return (Expression)genericMethod.Invoke(null, new[] { @delegate });
	    }


	    static Expression<TDelegate> ToExpression<TDelegate>(this Delegate @delegate)
		{
		    var context = @delegate.Target;
			return ConvertMethod<TDelegate>(@delegate.Method);
		}

        internal static Expression<TDelegate> ConvertMethod<TDelegate>(MethodInfo method)
		{
			var paramDefs = method.GetParameters();
		    var methodBody = method.GetMethodBody();
		    if (methodBody != null)
		    {
		        var locals = methodBody.LocalVariables.OrderBy(lvi => lvi.LocalIndex).Select(lvi => Expression.Parameter(lvi.LocalType, "var" + lvi.LocalIndex)).ToArray();
		        var parameters = ResolveParameters(method, paramDefs);
		        var ex = Processor.Create(locals, parameters).Process(method.GetInstructions().First(), method.ReturnType);
                return Expression.Lambda<TDelegate>(ex, parameters.Except(new[] {parameters.First()}));
		    }
		    return null;
		} 
        
        internal static Expression ConvertMethod(MethodInfo method)
		{
			var paramDefs = method.GetParameters();
		    var methodBody = method.GetMethodBody();
		    if (methodBody != null)
		    {
		        var locals = methodBody.LocalVariables.OrderBy(lvi => lvi.LocalIndex).Select(lvi => Expression.Parameter(lvi.LocalType, "var" + lvi.LocalIndex)).ToArray();
		        var parameters = ResolveParameters(method, paramDefs);
		        var ex = Processor.Create(locals, parameters).Process(method.GetInstructions().First(), method.ReturnType);
		        return Expression.Lambda(ex, parameters);
		    }
		    return null;
		}

		private static ParameterExpression[] ResolveParameters(MethodInfo method, ParameterInfo[] parameters)
		{
			IList<ParameterExpression> args;
			Debug.Assert(method.DeclaringType != null);
			if (method.IsStatic)
				args = parameters
					.Select(p => Expression.Parameter(p.ParameterType, p.Name))
					.ToList();
			else
			{
				args = new[] {Expression.Parameter(method.DeclaringType, "this")}
					.Union(parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)))
					.ToList();
			}
			return args.ToArray();
		}
	}

    public class Processor
	{
		private const string cachedAnonymousMethodDelegate = "<>9__CachedAnonymousMethodDelegate";

		public static Processor Create(ParameterExpression[] locals, IList<ParameterExpression> args)
		{
			return new Processor(new Stack<Expression>(), locals, args);
		}

		Processor Clone()
		{
			return new Processor(new Stack<Expression>(/* stack */), locals, args.ToArray());
		}

		static readonly MethodInfo stringConcat = typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) });

		readonly Stack<Expression> stack;
		readonly ParameterExpression[] locals;
		readonly IList<ParameterExpression> args;
	    private Instruction instruction;
	    private Dictionary<OpCode, Action> table;

	    Processor(Stack<Expression> stack, ParameterExpression[] locals, IList<ParameterExpression> args)
		{
			this.stack = stack;
			this.locals = locals;
			this.args = args;
            CreateHashMap();
		}

	    void Push(Expression e)
	    {
	        stack.Push(e);
	    }

	    Expression Pop()
	    {
	        return stack.Pop();
	    }

	    public Expression Process(Instruction start, Type returnType)
	    {
	        var ex = AdjustType(Process(start), returnType);

	        if (ex.Type != returnType && returnType != typeof(void))
	        {
	            return Expression.Convert(ex, returnType);
	        }

	        return ex;
	    }

	    void CreateHashMap()
	    {
	        table = new Dictionary<OpCode, Action>
	        {
	            {OpCodes.Nop, Nop},
	            {OpCodes.Break, Break},
	            {OpCodes.Ldarg_0, () => LdArg(0)},
	            {OpCodes.Ldarg_1, () => LdArg(1)},
	            {OpCodes.Ldarg_2, () => LdArg(2)},
	            {OpCodes.Ldarg_3, () => LdArg(3)},
	            {OpCodes.Ldarg_S, () => LdArg((short) instruction.Operand)},
	            {OpCodes.Ldarg, () => LdArg((int) instruction.Operand)},
	            {OpCodes.Ldarga, Ldarga},
	            {OpCodes.Ldarga_S, Ldarga},
	            {OpCodes.Ldlen, Ldlen},
	            {OpCodes.Ldelem, LdElem},
	            {OpCodes.Ldelem_I, LdElem},
	            {OpCodes.Ldelem_I1, LdElem},
	            {OpCodes.Ldelem_I2, LdElem},
	            {OpCodes.Ldelem_I4, LdElem},
	            {OpCodes.Ldelem_I8, LdElem},
	            {OpCodes.Ldelem_U1, LdElem},
	            {OpCodes.Ldelem_U2, LdElem},
	            {OpCodes.Ldelem_U4, LdElem},
	            {OpCodes.Ldelem_R4, LdElem},
	            {OpCodes.Ldelem_R8, LdElem},
	            {OpCodes.Ldelem_Ref, LdElem},
	            {OpCodes.Stloc_0, () => StLoc(0)},
	            {OpCodes.Stloc_1, () => StLoc(1)},
	            {OpCodes.Stloc_2, () => StLoc(2)},
	            {OpCodes.Stloc_3, () => StLoc(3)},
	            {OpCodes.Stloc_S, () => StLoc((byte) instruction.Operand)},
	            {OpCodes.Stloc, () => StLoc((int) instruction.Operand)},
	            {OpCodes.Stelem, StElem},
	            {OpCodes.Stelem_I, StElem},
	            {OpCodes.Stelem_I1, StElem},
	            {OpCodes.Stelem_I2, StElem},
	            {OpCodes.Stelem_I4, StElem},
	            {OpCodes.Stelem_I8, StElem},
	            {OpCodes.Stelem_R4, StElem},
	            {OpCodes.Stelem_R8, StElem},
	            {OpCodes.Stelem_Ref, StElem},
	            {OpCodes.Ldnull, LdNull},
	            {OpCodes.Ldfld, LdFld},
	            {OpCodes.Ldflda, LdFld},
	            {OpCodes.Ldsfld, Ldsfld},
	            {OpCodes.Ldloc_0, () => LdLoc(0)},
	            {OpCodes.Ldloc_1, () => LdLoc(1)},
	            {OpCodes.Ldloc_2, () => LdLoc(2)},
	            {OpCodes.Ldloc_3, () => LdLoc(3)},
	            {OpCodes.Ldloc, () => LdLoc((int) instruction.Operand)},
	            {OpCodes.Ldloc_S, () => LdLoc(((LocalVariableInfo) instruction.Operand).LocalIndex)},
	            {OpCodes.Ldloca, LdLoca},
	            {OpCodes.Ldloca_S, LdLoca},
	            {OpCodes.Ldstr, Ldstr},
	            {OpCodes.Ldc_I4_0, () => LdC(0)},
	            {OpCodes.Ldc_I4_1, () => LdC(1)},
	            {OpCodes.Ldc_I4_2, () => LdC(2)},
	            {OpCodes.Ldc_I4_3, () => LdC(3)},
	            {OpCodes.Ldc_I4_4, () => LdC(4)},
	            {OpCodes.Ldc_I4_5, () => LdC(5)},
	            {OpCodes.Ldc_I4_6, () => LdC(6)},
	            {OpCodes.Ldc_I4_7, () => LdC(7)},
	            {OpCodes.Ldc_I4_8, () => LdC(8)},
	            {OpCodes.Ldc_I4_S, () => LdC((sbyte) instruction.Operand)},
	            {OpCodes.Ldc_I4_M1, () => LdC(-1)},
	            {OpCodes.Ldc_I4, () => LdC((int) instruction.Operand)},
	            {OpCodes.Ldc_I8, () => LdC((long) instruction.Operand)},
	            {OpCodes.Ldc_R4, () => LdC((float) instruction.Operand)},
	            {OpCodes.Ldc_R8, () => LdC((double) instruction.Operand)},
	            {OpCodes.Br_S, Br},
	            {OpCodes.Br, Br},
	            {OpCodes.Brfalse, Brfalse},
	            {OpCodes.Brfalse_S, Brfalse},
	            {OpCodes.Brtrue, Brtrue},
	            {OpCodes.Brtrue_S, Brtrue},
	            {OpCodes.Bgt, Bgt},
	            {OpCodes.Bgt_S, Bgt},
	            {OpCodes.Bgt_Un, Bgt},
	            {OpCodes.Bgt_Un_S, Bgt},
	            {OpCodes.Bge, Bge},
	            {OpCodes.Bge_S, Bge},
	            {OpCodes.Bge_Un, Bge},
	            {OpCodes.Bge_Un_S, Bge},
	            {OpCodes.Blt, Blt},
	            {OpCodes.Blt_S, Blt},
	            {OpCodes.Blt_Un, Blt},
	            {OpCodes.Blt_Un_S, Blt},
	            {OpCodes.Ble, Ble},
	            {OpCodes.Ble_S, Ble},
	            {OpCodes.Ble_Un, Ble},
	            {OpCodes.Ble_Un_S, Ble},
	            {OpCodes.Beq, Beq},
	            {OpCodes.Beq_S, Beq},
	            {OpCodes.Bne_Un, BneUn},
	            {OpCodes.Bne_Un_S, BneUn},
	            {OpCodes.Dup, Dup},
	            {OpCodes.Pop, Pop_},
	            {OpCodes.Add, Add},
	            {OpCodes.Add_Ovf, Add_Ovf},
	            {OpCodes.Add_Ovf_Un, Add_Ovf},
	            {OpCodes.Sub, Sub},
	            {OpCodes.Sub_Ovf, Sub_Ovf},
	            {OpCodes.Sub_Ovf_Un, Sub_Ovf},
	            {OpCodes.Mul, Mul},
	            {OpCodes.Mul_Ovf, Mul_Ovf},
	            {OpCodes.Mul_Ovf_Un, Mul_Ovf},
	            {OpCodes.Div, Div},
	            {OpCodes.Div_Un, Div},
	            {OpCodes.Rem, Rem},
	            {OpCodes.Rem_Un, Rem},
	            {OpCodes.Xor, Xor},
	            {OpCodes.Shl, Shl},
	            {OpCodes.Shr, Shr},
	            {OpCodes.Shr_Un, Shr},
	            {OpCodes.Neg, Neg},
	            {OpCodes.Not, Not},
	            {OpCodes.And, And},
	            {OpCodes.Or, Or},
	            {OpCodes.Conv_I, () => Conv(typeof (int))},
	            {OpCodes.Conv_I1, () => Conv(typeof (sbyte))},
	            {OpCodes.Conv_I2, () => Conv(typeof (short))},
	            {OpCodes.Conv_I4, () => Conv(typeof (int))},
	            {OpCodes.Conv_I8, () => Conv(typeof (long))},
	            {OpCodes.Conv_U, () => Conv(typeof (uint))},
	            {OpCodes.Conv_U1, () => Conv(typeof (byte))},
	            {OpCodes.Conv_U2, () => Conv(typeof (ushort))},
	            {OpCodes.Conv_U4, () => Conv(typeof (uint))},
	            {OpCodes.Conv_U8, () => Conv(typeof (ulong))},
	            {OpCodes.Conv_Ovf_I, () => ConvChecked(typeof (int))},
	            {OpCodes.Conv_Ovf_I1, () => ConvChecked(typeof (sbyte))},
	            {OpCodes.Conv_Ovf_I2, () => ConvChecked(typeof (short))},
	            {OpCodes.Conv_Ovf_I4, () => ConvChecked(typeof (int))},
	            {OpCodes.Conv_Ovf_I8, () => ConvChecked(typeof (long))},
	            {OpCodes.Conv_Ovf_U, () => ConvChecked(typeof (uint))},
	            {OpCodes.Conv_Ovf_U1, () => ConvChecked(typeof (byte))},
	            {OpCodes.Conv_Ovf_U2, () => ConvChecked(typeof (ushort))},
	            {OpCodes.Conv_Ovf_U4, () => ConvChecked(typeof (uint))},
	            {OpCodes.Conv_Ovf_U8, () => ConvChecked(typeof (ulong))},
	            {OpCodes.Conv_Ovf_I_Un, () => ConvChecked(typeof (int))},
	            {OpCodes.Conv_Ovf_I1_Un, () => ConvChecked(typeof (sbyte))},
	            {OpCodes.Conv_Ovf_I2_Un, () => ConvChecked(typeof (short))},
	            {OpCodes.Conv_Ovf_I4_Un, () => ConvChecked(typeof (int))},
	            {OpCodes.Conv_Ovf_I8_Un, () => ConvChecked(typeof (long))},
	            {OpCodes.Conv_Ovf_U_Un, () => ConvChecked(typeof (uint))},
	            {OpCodes.Conv_Ovf_U1_Un, () => ConvChecked(typeof (byte))},
	            {OpCodes.Conv_Ovf_U2_Un, () => ConvChecked(typeof (ushort))},
	            {OpCodes.Conv_Ovf_U4_Un, () => ConvChecked(typeof (uint))},
	            {OpCodes.Conv_Ovf_U8_Un, () => ConvChecked(typeof (ulong))},
	            {OpCodes.Conv_R4, () => ConvChecked(typeof (float))},
	            {OpCodes.Conv_R_Un, () => ConvChecked(typeof (float))},
	            {OpCodes.Conv_R8, () => ConvChecked(typeof (double))},
	            {OpCodes.Castclass, () => Conv((Type) instruction.Operand)},
	            {OpCodes.Newobj, NewObj},
	            {OpCodes.Newarr, NewArr},
	            {OpCodes.Box, Box},
	            {OpCodes.Call, Call},
	            {OpCodes.Callvirt, Call},
	            {OpCodes.Ceq, Ceq},
	            {OpCodes.Cgt, Cgt},
	            {OpCodes.Cgt_Un, Cgt},
	            {OpCodes.Clt, Clt},
	            {OpCodes.Clt_Un, Clt},
	            {OpCodes.Ret, Ret}
	        };
	    }

	    private void Ret()
	    {
	        instruction = null;
	    }


	    public Expression Process(Instruction start, Instruction last = null)
	    {
	        instruction = start;
			while (instruction != null && instruction != last)
			{
				Debug.WriteLine(instruction);
			    var wasInstruction = instruction;
			    table[instruction.OpCode]();
			    if (wasInstruction == instruction)
			    {
                    // branches or macros already advanced the instruction.
			        instruction = instruction.Next;
			    }
			}
            var varUsageCounter = new CountLocalsVisitor(locals);
	        foreach (var expr in stack)
	            varUsageCounter.Visit(expr);

	        var result = stack.Count == 0
	            ? Expression.Empty()
	            : stack.Count == 1
	                ? Pop()
	                : Expression.Block(varUsageCounter.UsedVars(), stack.Reverse());

	        return result;
		}

        private void Clt()
	    {
	        var val1 = Pop();
	        var val2 = Pop();

	        Push(Expression.LessThan(val2, AdjustType(val1, val2.Type)));
	    }

	    private void Cgt()
	    {
	        var val1 = Pop();
	        var val2 = Pop();

	        Push(Expression.GreaterThan(val2, AdjustType(val1, val2.Type)));
	    }

	    private void Ceq()
	    {
	        var val1 = Pop();
	        var val2 = Pop();

	        Push(AdjustedBinaryExpression(val2, AdjustType(val1, val2.Type), ExpressionType.Equal));
	    }

	    private void Box()
	    {
	        var expression = Pop();
            var type = (Type)instruction.Operand;
            var constantExpression = expression as ConstantExpression;
	        if (constantExpression != null && type.IsEnum)
	            Push(Expression.Convert(Expression.Constant(Enum.ToObject(type, constantExpression.Value)), typeof (Enum)));
	        else
	            Push(Expression.Convert(expression, expression.Type.IsEnum ? typeof (Enum) : typeof (object)));
	    }

	    private void NewArr()
	    {
	        var operand = (Type)instruction.Operand;
	        var expression = Pop();
	        var size = expression as ConstantExpression;
	        if (size != null && (int)size.Value == 0) 
	            Push(Expression.NewArrayInit(operand));
	        else
	            Push(Expression.NewArrayBounds(operand, expression));
	    }

	    private void NewObj()
	    {
	        var operand = (ConstructorInfo)instruction.Operand;
	        Push(Expression.New(operand, GetArguments(operand)));
	    }

	    private void Or()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.Or(val2, val1));
	    }

	    private void And()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.And(val2, val1));
	    }

	    private void ConvChecked(Type type)
	    {
	        Push(Expression.ConvertChecked(Pop(), type));
        }


	    private void Conv(Type type)
	    {
	        var val1 = Pop();
	        Push(Expression.Convert(val1, type)); 
	    }

	    private void Not()
	    {
	        var val = Pop();
	        Push(Expression.Not(val));
	    }

	    private void Neg()
	    {
	        var val = Pop();
	        Push(Expression.Negate(val));
	    }

	    private void Shr()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.RightShift(val2, val1));
	    }

	    private void Shl()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.LeftShift(val2, val1));
	    }

	    private void Xor()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.ExclusiveOr(val2, val1));
	    }

	    private void Rem()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.Modulo(val2, val1));
	    }

	    private void Div()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.Divide(val2, val1));
	    }

	    private void Mul_Ovf()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.MultiplyChecked(val2, val1));
	    }

	    private void Mul()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.Multiply(val2, val1));
	    }

	    private void Sub_Ovf()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.SubtractChecked(val2, val1));
	    }

	    private void Sub()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.Subtract(val2, val1));
	    }

	    private void Add_Ovf()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.AddChecked(val2, val1));
	    }

	    private void Add()
	    {
	        var val1 = Pop();
	        var val2 = Pop();
	        Push(Expression.Add(val2, val1));
	    }

	    private void Pop_()
	    {
	        Pop();
	    }

	    private void Dup()
	    {
	        Push(stack.Peek());
	    }

	    private void BneUn()
	    {
	        var val1 = Pop();
	        ConditionalBranch(val => AdjustedBinaryExpression(val, val1, ExpressionType.NotEqual));
	    }

	    private void Beq()
	    {
	        var val1 = Pop();
	        ConditionalBranch(val => AdjustedBinaryExpression(val, val1, ExpressionType.Equal));
	    }

	    private void Ble()
	    {
	        var val1 = Pop();
	        ConditionalBranch(val => Expression.LessThanOrEqual(val, val1));
	    }

	    private void Blt()
	    {
	        var val1 = Pop();
	        ConditionalBranch(val => Expression.LessThan(val, val1));
	    }

	    private void Bge()
	    {
	        var val1 = Pop();
	        ConditionalBranch(val => Expression.GreaterThanOrEqual(val, val1));
	    }

	    private void Bgt()
	    {
	        var val1 = Pop();
	        ConditionalBranch(val => Expression.GreaterThan(val, val1));
	    }

	    private void Brtrue()
	    {
	        var target = ((Instruction)instruction.Operand);

	        if (target.OpCode == OpCodes.Ldsfld && IsCachedAnonymousMethodDelegate(target.Operand as FieldInfo))
	        {
	            Push(DelegateExtensions.ConvertMethod((MethodInfo)instruction.Next.Next.Operand));
	            instruction = (Instruction)instruction.Operand;
	        }
	        else
	        {
	            ConditionalBranch(val => Expression.NotEqual(val, Default(val.Type)));
	        }
	    }

	    private void Brfalse()
	    {
	        ConditionalBranch(val => Expression.Equal(val, Default(val.Type)));
	    }

	    private void Br()
	    {
	        instruction = (Instruction)instruction.Operand;
	    }

	    private void Ldstr()
	    {
	        Push(Expression.Constant(instruction.Operand));
	    }

	    private void LdFld()
	    {
	        var instance = Pop();
            Push(Expression.Field(instance, (FieldInfo)instruction.Operand));
	    }

	    private void Ldsfld()
	    {
            var targetField = instruction.Operand as FieldInfo;

	        if (IsCachedAnonymousMethodDelegate(targetField))
	        {
	            // do nothing.
	        }
	        else
	        {
                Push(Expression.Field(null, (FieldInfo)instruction.Operand));
	        }

	    }

	    private void LdNull()
	    {
	        Push(Expression.Constant(null));
	    }

	    private void LdElem()
	    {
	        var index = Pop();
	        var array = Pop();
	        Push(Expression.ArrayIndex(array, index));

	    }

	    private void Ldlen()
	    {
	        var array = Pop();
	        Push(Expression.ArrayLength(array));

	    }

	    private void Ldarga()
	    {
            var operand = (ParameterInfo)instruction.Operand;
            Push(args.Single(x => x.Name == operand.Name));
        }

	    private void Nop()
	    {
	    }

        private void Break()
        {
        }

        private void LdLoca()
        {
            var operand = (LocalVariableInfo)instruction.Operand;
            if (!HandleInitStruct(operand))
                LdLoc(operand.LocalIndex);
        }


	    private bool HandleInitStruct(LocalVariableInfo operand)
	    {
            // when we have initialization of value type, handle it as type.Default()
            // for  example, (int?)null
            // ldloca.s   CS$0$0000
            // initobj    valuetype [mscorlib]System.Nullable`1<int32>
            // ldloc.s    CS$0$0000
	        var initInstr = instruction.Next;
	        if (initInstr == null || initInstr.OpCode != OpCodes.Initobj)
	            return false;

	        var ldlocInstr = initInstr.Next;
	        if (ldlocInstr == null)
	            return false;

            OpCode ldloc;
	        switch (operand.LocalIndex)
	        {
	            case 0:
	                ldloc = OpCodes.Ldloc_0;
	                break;
	            case 1:
	                ldloc = OpCodes.Ldloc_1;
	                break;
	            case 2:
	                ldloc = OpCodes.Ldloc_2;
	                break;
	            case 3:
	                ldloc = OpCodes.Ldloc_3;
	                break;
	            default:
	                ldloc = instruction.OpCode == OpCodes.Ldloca ? OpCodes.Ldloc : OpCodes.Ldloc_S;
	                break;
	        }

	        if (ldlocInstr.OpCode != ldloc)
	            return false;

			Type type = operand.LocalType;
			Push(Expression.Default(type));
	        instruction = ldlocInstr.Next;
	        return true;
	    }

	    private static bool IsCachedAnonymousMethodDelegate(FieldInfo field)
		{
			return field != null &&
				field.Name.Contains(cachedAnonymousMethodDelegate) &&
				Attribute.IsDefined(field, typeof(CompilerGeneratedAttribute), false);
		}

		private static BinaryExpression AdjustedBinaryExpression(Expression left, Expression right, ExpressionType expressionType)
		{
			left = ConvertEnumExpressionToUnderlyingType(left);
			right = ConvertEnumExpressionToUnderlyingType(right);

			return Expression.MakeBinary(expressionType, left, right);
		}

		private static Expression ConvertEnumExpressionToUnderlyingType(Expression expression)
		{
			if (expression.Type.IsEnum)
				return Expression.Convert(expression, expression.Type.GetEnumUnderlyingType());

			return expression;
		}

		static Expression Default(Type type)
		{
			if (type == typeof(bool))
				return Expression.Constant(false);
			if (type.IsValueType)
				return Expression.Default(type);
			return Expression.Constant(null, type);
		}

		void ConditionalBranch(Func<Expression, BinaryExpression> condition)
		{
			var val1 = Pop();
			var test = condition(val1);

			var ifTrue = (Instruction)instruction.Operand;
			var ifFalse = instruction.Next;

			Instruction common = GetJoinPoint(ifTrue, ifFalse);

			var ifFalseExpr = Clone().Process(ifFalse, common);
		    var ifTrueExpr = Clone().Process(ifTrue, common);
		    
            ifTrueExpr = AdjustType(ifTrueExpr, ifFalseExpr.Type);

            var expression = BuildConditionalBranch(test, val1, ifTrueExpr, ifFalseExpr);
			Push(expression);
			instruction = common;
		}

		private static Expression BuildConditionalBranch(BinaryExpression test, Expression val1, Expression ifTrue, Expression ifFalse)
		{
			var ifTrueConst = ifTrue as ConstantExpression;
			if (ifTrueConst != null && ifTrueConst.Value is bool)
			{
				if ((bool)ifTrueConst.Value)
				{
					if (test.NodeType == ExpressionType.Equal ||
						test.NodeType == ExpressionType.NotEqual ||
						test.NodeType == ExpressionType.GreaterThan ||
						test.NodeType == ExpressionType.GreaterThanOrEqual ||
						test.NodeType == ExpressionType.LessThan ||
						test.NodeType == ExpressionType.LessThanOrEqual)
					{
						return Expression.OrElse(test, ifFalse);
					}
				}
				else
				{
					switch (test.NodeType)
					{
						case ExpressionType.Equal:
							return Expression.AndAlso(Expression.NotEqual(test.Left, test.Right), ifFalse);
						case ExpressionType.NotEqual:
							return Expression.AndAlso(Expression.Equal(test.Left, test.Right), ifFalse);
						case ExpressionType.GreaterThan:
							return Expression.AndAlso(Expression.LessThanOrEqual(test.Left, test.Right), ifFalse);
						case ExpressionType.GreaterThanOrEqual:
							return Expression.AndAlso(Expression.LessThan(test.Left, test.Right), ifFalse);
						case ExpressionType.LessThan:
							return Expression.AndAlso(Expression.GreaterThanOrEqual(test.Left, test.Right), ifFalse);
						case ExpressionType.LessThanOrEqual:
							return Expression.AndAlso(Expression.GreaterThan(test.Left, test.Right), ifFalse);
					}
				}
			}
			if (test.NodeType == ExpressionType.NotEqual)
			{
				if (val1 == ifTrue && (test.Right is DefaultExpression || (test.Right is ConstantExpression && ((ConstantExpression)test.Right).Value == null)))
				{
					return Expression.Coalesce(val1, ifFalse);
				}
			}
            return Expression.Condition(test, ifTrue, ifFalse, ifTrue.Type);
		}

		static Instruction GetJoinPoint(Instruction left, Instruction right)
		{
			return GetCommon(GetFlow(left), GetFlow(right));
		}

		static Instruction GetCommon(IEnumerable<Instruction> leftFlow, Stack<Instruction> rightFlow)
		{
			Instruction instruction = null;
			foreach (var left in leftFlow)
			{
				if (rightFlow.Count <= 0 || left != rightFlow.Pop())
					break;

				instruction = left;
			}
			return instruction;
		}

		static Stack<Instruction> GetFlow(Instruction instruction)
		{
			var instructions = new Stack<Instruction>();
			while (instruction != null)
			{
				instructions.Push(instruction);

				if (instruction.OpCode.FlowControl == FlowControl.Return)
					break;

				if (instruction.OpCode.FlowControl == FlowControl.Branch)
				{
					instruction = (Instruction)instruction.Operand;
				}
				else if (instruction.OpCode.FlowControl == FlowControl.Cond_Branch)
				{
					instruction = GetJoinPoint((Instruction)instruction.Operand, instruction.Next);
				}
				else
				{
					instruction = instruction.Next;
				}
			}
			return instructions;
		}

		static Expression AdjustType(Expression expression, Type type)
		{
			var constantExpression = expression as ConstantExpression;
			if (constantExpression != null)
			{
				if (constantExpression.Value == null)
				{
					return Expression.Constant(null, type);
				}
				if (expression.Type == typeof(int) && type == typeof(bool))
				{
					return Expression.Constant(!Equals(constantExpression.Value, 0));
				}
			}
			else if (expression != null)
			{
			    if (expression.Type == typeof (int) && type == typeof (bool))
			    {
			        return Expression.NotEqual(expression, Expression.Constant(0));
			    }

			    if (!type.IsAssignableFrom(expression.Type) && expression.Type.IsEnum &&
			        expression.Type.GetEnumUnderlyingType() == type)
			    {
			        return Expression.Convert(expression, type);
			    }
			}
		    return expression;
		}

		void StElem()
		{
			var value = Pop();
			var index = Pop();
			var array = Pop();

			var newArray = array as NewArrayExpression;
			if (newArray != null)
			{
				var expressions = CreateArrayInitExpressions(newArray, value);
				//UpdateLocals(newArray, Expression.NewArrayInit(array.Type.GetElementType(), expressions));
                //        void UpdateLocals(ParameterExpression oldExpression, ParameterExpression newExpression)
                //		{
                //			for (var i = 0; i < locals.Length; i++)
                //			{
                //				var local = locals[i];
                //				if (local == oldExpression)
                //				{
                //					locals[i] = newExpression;
                //				}
                //			}
                //		}
                //
				return;
			}

			throw new NotImplementedException();
		}

		static IEnumerable<Expression> CreateArrayInitExpressions(NewArrayExpression newArray, Expression value)
		{
			if (newArray.NodeType == ExpressionType.NewArrayInit)
			{
				var expressions = newArray.Expressions.ToList();
				expressions.Add(value);
				return expressions;
			}
			return new[] { value };
		}

		void LdC(int i)
		{
			Push(Expression.Constant(i));
		}

		void LdC(long i)
		{
			Push(Expression.Constant(i));
		}

		void LdC(float i)
		{
			Push(Expression.Constant(i));
		}

		void LdC(double i)
		{
			Push(Expression.Constant(i));
		}

		void Call()
		{
            var m = (MethodInfo)instruction.Operand;
            var mArgs = GetArguments(m);

			var instance = m.IsStatic ? null : GetCallInstance();
			Push(BuildMethodCallExpression(m, instance, mArgs));
		}

	    private Expression GetCallInstance()
	    {
            // we could have parameter assigned to local var inside call. for example, Ext.CreateTabPanel("title") -> 
            // IL_0040: ldfld Entropy.Ext.IExtFactory Ext
            // IL_0045: ldstr "title"
            // IL_004a: stloc.3   <- this assignment is inside call constructor
            // ... load other params
            // IL_009e: ldloc.3
            // IL_009f: callvirt Entropy.Ext.ITabPanel 

	        var relocateAssignments = new Stack<Expression>();

	        Expression result = Pop();
	        while (result.NodeType == ExpressionType.Assign)
	        {
	            relocateAssignments.Push(result);
	            result = Pop();
	        }
              
            while(relocateAssignments.Any())
                stack.Push(relocateAssignments.Pop());
	        return result;
	    }

	    Expression[] GetArguments(MethodBase m)
		{
			var parameterInfos = m.GetParameters();
			var mArgs = new Expression[parameterInfos.Length];
			for (var i = parameterInfos.Length - 1; i >= 0; i--)
			{
				mArgs[i] = Pop();
			}
			return mArgs;
		}

		Expression BuildMethodCallExpression(MethodInfo m, Expression instance, Expression[] arguments)
		{
			if (m.Name == "Add" && instance != null && typeof(IEnumerable).IsAssignableFrom(instance.Type))
			{
				var newExpression = instance as NewExpression;
				if (newExpression != null)
				{
					var init = Expression.ListInit(newExpression, Expression.ElementInit(m, arguments));
					//UpdateLocals(newExpression, init);
					return init;
				}
				var initExpression = instance as ListInitExpression;
				if (initExpression != null)
				{
					var initializers = initExpression.Initializers.ToList();
					initializers.Add(Expression.ElementInit(m, arguments));
					var init = Expression.ListInit(initExpression.NewExpression, initializers);
					//UpdateLocals(initExpression, init);
					return init;
				}
			}
			if (m.IsSpecialName && m.IsHideBySig)
			{
				if (m.Name.StartsWith("get_"))
				{
					return Expression.Property(instance, m);
				}
				if (m.Name.StartsWith("op_"))
				{
					ExpressionType type;
					if (Enum.TryParse(m.Name.Substring(3), out type))
					{
						switch (arguments.Length)
						{
							case 1:
								return Expression.MakeUnary(type, arguments[0], arguments[0].Type);
							case 2:
								return Expression.MakeBinary(type, arguments[0], arguments[1]);
						}
					}
				}
			}
			if (m.Name == "Concat" && m.DeclaringType == typeof(string))
			{
				var expressions = GetExpressionsForStringConcat(arguments);
				if (expressions.Count > 1)
				{
					var expression = expressions[0];
					for (var i = 1; i < expressions.Count; i++)
					{
						expression = Expression.Add(expression, expressions[i], stringConcat);
					}
					return expression;
				}
			}

			var parameters = m.GetParameters();
			for (int i = 0; i < parameters.Length; i++)
			{
				var parameter = parameters[i];
				var argument = arguments[i];
				var parameterType = parameter.ParameterType;
				arguments[i] = AdjustType(argument, parameterType);
			}

			if (instance != null)
				return Expression.Call(AdjustType(instance, m.DeclaringType), m, arguments);

			return Expression.Call(null, m, arguments);
		}

		static IList<Expression> GetExpressionsForStringConcat(Expression[] arguments)
		{
			if (arguments.Length == 1)
			{
				var array = arguments[0] as NewArrayExpression;
				if (array != null)
				{
					if (array.NodeType == ExpressionType.NewArrayInit)
					{
						return array.Expressions;
					}
				}
			}
			return arguments;
		}

		void LdLoc(int index)
		{
			Push(locals[index]);
		}

		void StLoc(int index)
		{
            Push(Expression.Assign(locals[index], Pop()));
		}

		void LdArg(int index)
		{
			Push(args[index]);
		}
	}

    internal class CountLocalsVisitor : ExpressionVisitor
    {
        private readonly Dictionary<ParameterExpression, int> locals;

        internal IEnumerable<ParameterExpression> UsedVars()
        {
            return locals.Where(pair => pair.Value > 0).Select(pair => pair.Key);
        }

        internal CountLocalsVisitor(ParameterExpression[] locals)
        {
            this.locals  = locals.ToDictionary(e => e, e => 0);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            int count;
            if (locals.TryGetValue(node, out count))
            {
                locals[node] = count + 1;
            }
            return node;
        }
    }
}
